package educrypt.gui.ctr;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFileChooser;
import javax.swing.SwingWorker;

import educrypt.analysis.Frequencies;
import educrypt.analysis.FrequencyAnalysis;
import educrypt.ciphers.Caesar;
import educrypt.ciphers.Vigenere;
import educrypt.ciphers.hill.Hill;
import educrypt.ciphers.hill.HillKeyMatrix;
import educrypt.ciphers.playfair.Playfair;
import educrypt.commons.Alphabet;
import educrypt.commons.Global;
import educrypt.commons.Text;
import educrypt.cryptanalysis.FrequencyBasedCryptAnalysis;
import educrypt.cryptanalysis.hill.CryptAnalysisHill;
import educrypt.cryptanalysis.playfair.CryptAnalysisPlayfair;
import educrypt.cryptanalysis.vigenere.CryptAnalysisVigenere;
import educrypt.database.dao.AlphabetDAO;
import educrypt.database.dao.FrequencyAnalysisDAO;
import educrypt.database.dao.TextLanguageDAO;
import educrypt.database.dao.TextTypeDAO;
import educrypt.exceptions.EduCryptException;
import educrypt.gui.view.CryptanalysisHillView;
import educrypt.gui.view.CryptanalysisOptionsView;
import educrypt.gui.view.CryptanalysisPlayfairView;
import educrypt.gui.view.CryptanalysisResultView;
import educrypt.gui.view.EditorTextOptionsView;
import educrypt.gui.view.EducryptFileChooserView;
import educrypt.gui.view.MainView;
import educrypt.gui.view.TextOptionsView;
import educrypt.gui.view.TextPreviewView;
import educrypt.gui.view.TextView;
import educrypt.i18n.Messages;
import educrypt.io.Input;

public class CryptanalysisCtr implements ActionListener, Observer {

	// View
	private MainView view;
	private TextView textView;
	private TextOptionsView textOptionsView;
	private CryptanalysisOptionsView optionsView;
	private CryptanalysisResultView resultView;
	private TextPreviewView textPreviewView;
	private EditorTextOptionsView editorTextTypeView, editorTextLanguageView;

	private CryptanalysisHillView cryptanalysisHillView;
	private CryptanalysisPlayfairView cryptanalysisPlayfairView;

	// Model
	private Text originalText;
	private List<Alphabet> alphabets;
	private List<String> textTypes, textLanguages;

	public CryptanalysisCtr(MainView view) {
		// Panels
		this.view = view;

		this.textView = view.getCryptanalysisPanel().getTextPanel();
		this.textOptionsView = view.getCryptanalysisPanel()
				.getTextOptionsPanel();
		this.optionsView = view.getCryptanalysisPanel().getOptionsPanel();
		this.resultView = view.getCryptanalysisPanel()
				.getCryptanalysisResultPanel();
		// Dialogs
		this.textPreviewView = view.getTextPreviewDialog();
		this.editorTextTypeView = view.getEditorTextTypeDialog();
		this.editorTextLanguageView = view.getEditorTextLanguageDialog();
		this.cryptanalysisHillView = this.optionsView
				.getCryptanalysisHillDialog();
		this.cryptanalysisPlayfairView = this.optionsView
				.getCryptanalysisPlayfairDialog();
		initializeValues();
	}

	private void initializeValues() {
		// Model
		this.textTypes = TextTypeDAO.getInstance().findAll();
		this.textLanguages = TextLanguageDAO.getInstance().findAll();

		// Model<->View
		this.textOptionsView.showTypes(textTypes);
		this.textOptionsView.showLanguages(textLanguages);
		this.optionsView.showCiphers(Global.CIPHERS_LIST);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		try {
			String command = e.getActionCommand();
			// File / Text
			if (command.equals(TextView.FILE_BROWSE)) {
				textView.showFileChooser();
			}
			if (command.equals(TextView.PREVIEW)) {
				textPreviewView.showPreview(originalText.getContent());
			}
			// Text Options
			if (command.equals(TextOptionsView.LANGUAGE_EDITOR)) {
				editorTextLanguageView.open((Component) this.textOptionsView);

			}
			if (command.equals(TextOptionsView.TYPE_EDITOR)) {
				editorTextTypeView.open((Component) this.textOptionsView);
			}
			// Cryptanalysis Options
			if (command.equals(CryptanalysisOptionsView.CIPHER_SELECTED)) {
				processCipherSelected();
			}

			if (command.equals(CryptanalysisOptionsView.ALPHABET_SELECTED)) {
				if (optionsView.getSelectedAlphabetIndex() > 0) {
					optionsView.enableBreak(true);
				} else {
					optionsView.enableBreak(false);
				}
			}
			if (command.equals(CryptanalysisOptionsView.BREAK)) {
				breakCipher();
			}

			if (command.equals(CryptanalysisOptionsView.BREAK_PLAYFAIR)) {
				breakPlayfairCipher();
			}

			if (command.equals(CryptanalysisHillView.BREAK_HILL)) {
				breakHillCipher();
			}

			// LoadView - SaveView
			if (command.equals(JFileChooser.APPROVE_SELECTION)) {
				JFileChooser fileChooser = (JFileChooser) e.getSource();
				int type = fileChooser.getDialogType();
				if (type == JFileChooser.OPEN_DIALOG) {
					this.processOpenFile();
				} else if (type == JFileChooser.SAVE_DIALOG) {
					// Nothing to do
				}
			}
		} catch (EduCryptException ex) {
			view.showExceptionDialog(ex.getLocalizedMessage());
		}

	}

	private void processOpenFile() throws EduCryptException {
		EducryptFileChooserView fileChooser = textView.getFileChooser();
		File selectedFile = fileChooser.getSelectedFile();
		if (selectedFile.exists()) {
			textView.setFilePath(selectedFile.getAbsolutePath());
			textView.enablePreview(true);
			originalText = new Text();
			String textContent = Input.readTextFromFile(selectedFile
					.getAbsolutePath());
			originalText.setContent(textContent);
			textOptionsView.setPanelEnabled(true);
			optionsView.setPanelEnabled(true);
			resultView.setPanelEnabled(false);
		}
	}

	private void processCipherSelected() {
		int cipher = optionsView.getSelectedCipherIndex();
		if (cipher > 0) {
			showAlphabets(cipher);
		} else {
			optionsView.hideAlphabets();
		}
	}

	private void showAlphabets(int cipher) {
		this.alphabets = AlphabetDAO.getInstance().findByCipher(cipher);
		List<String> alphabetsNames = new ArrayList<String>();
		for (Alphabet alphabet : alphabets) {
			alphabetsNames.add(alphabet.getName());
		}
		optionsView.showAlphabets(alphabetsNames);
	}

	private void breakCipher() throws EduCryptException {
		if (textOptionsView.getSelectedLanguageIndex() > 0) {
			int selectedCipher = optionsView.getSelectedCipherIndex();
			int selectedAlphabetIndex = optionsView.getSelectedAlphabetIndex() - 1;

			Alphabet selectedAlphabet = this.alphabets
					.get(selectedAlphabetIndex);
			this.originalText.setType(textOptionsView.getSelectedType());
			this.originalText
					.setLanguage(textOptionsView.getSelectedLanguage());

			switch (selectedCipher) {
			case Global.CAESAR:
				CaesarCryptanalysisWorker caesarCryptanalysisWorker = new CaesarCryptanalysisWorker(
						selectedAlphabet, originalText);
				caesarCryptanalysisWorker.execute();
				this.view.showWaitDialog(true);

				break;
			case Global.VIGENERE:
				VigenereCryptanalysisWorker vigenereCryptanalysisWorker = new VigenereCryptanalysisWorker(
						selectedAlphabet, originalText);
				vigenereCryptanalysisWorker.execute();
				this.view.showWaitDialog(true);

				break;
			case Global.PLAYFAIR:
				this.optionsView.getCryptanalysisPlayfairDialog().open();

				break;
			case Global.HILL:
				this.cryptanalysisHillView.open();
				break;
			default:
				break;
			}
		} else {
			throw new EduCryptException(
					Messages.getString("msgSelectTextCharacteristics"));
		}

	}

	private void breakPlayfairCipher() {
		int selectedAlphabetIndex = optionsView.getSelectedAlphabetIndex() - 1;

		Alphabet selectedAlphabet = this.alphabets.get(selectedAlphabetIndex);
		this.originalText.setType(textOptionsView.getSelectedType());
		this.originalText.setLanguage(textOptionsView.getSelectedLanguage());
		int maxGenerations = this.cryptanalysisPlayfairView.getMaxGenerations();
		int maxCandidates = this.cryptanalysisPlayfairView.getMaxCandidates();
		int survivorsPercentage = this.cryptanalysisPlayfairView.getSurvivors();
		PlayfairCryptanalysisWorker playfairCryptanalysisWorker = new PlayfairCryptanalysisWorker(
				selectedAlphabet, originalText, maxCandidates,
				survivorsPercentage, maxGenerations);
		playfairCryptanalysisWorker.execute();
		this.cryptanalysisPlayfairView.close();
		this.view.showWaitDialog(true);
	}

	private void breakHillCipher() throws EduCryptException {
		int selectedAlphabetIndex = optionsView.getSelectedAlphabetIndex() - 1;

		Alphabet selectedAlphabet = this.alphabets.get(selectedAlphabetIndex);
		this.originalText.setType(textOptionsView.getSelectedType());
		this.originalText.setLanguage(textOptionsView.getSelectedLanguage());

		int keyLength = this.cryptanalysisHillView.getKeyLength();
		int ngramSize = (int) Math.sqrt(keyLength);
		String plainTextChunk = this.cryptanalysisHillView.getPlainTextChunk();
		// String cipherTextChunk = this.cryptanalysisHillView
		// .getCipherTextChunk();;

		HillCryptanalysisWorker hillCryptanalysisWorker = new HillCryptanalysisWorker(
				selectedAlphabet, originalText, plainTextChunk, ngramSize);
		hillCryptanalysisWorker.execute();
		this.cryptanalysisHillView.close();
		this.view.showWaitDialog(true);
	}

	private class CaesarCryptanalysisWorker extends
			SwingWorker<List<Integer>, Void> {
		private Alphabet alphabet;
		// private String keyword;
		private Text plainText;
		private Text cipherText;

		public CaesarCryptanalysisWorker(Alphabet alphabet, Text cipherText) {
			this.alphabet = alphabet;
			this.cipherText = cipherText;
		}

		@Override
		protected List<Integer> doInBackground() throws Exception {
			final int CAESAR_NGRAM_SIZE = 1;

			// Analyzing cipher text
			FrequencyAnalysis cipherTextAnalysis = new FrequencyAnalysis(
					this.cipherText, this.alphabet, CAESAR_NGRAM_SIZE);

			// Getting average frequencies from Database
			Frequencies cipherTextFrequencies = cipherTextAnalysis
					.getFrequencies();

			Frequencies averageFrequencies = FrequencyAnalysisDAO.getInstance()
					.getAverageFrequencies(alphabet.getName(),
							CAESAR_NGRAM_SIZE, this.cipherText.getLanguage(),
							this.cipherText.getType());

			// Cryptanalyze text
			FrequencyBasedCryptAnalysis cryptanalysis = new FrequencyBasedCryptAnalysis(
					cipherTextFrequencies, averageFrequencies);

			// Return the possible offset ordered by probability
			List<List<String>> caesarKeyApproximation = cryptanalysis
					.cryptAnalyze();
			List<Integer> sortedOffsets = cryptanalysis
					.getSortedOffsetsFromCaesarCandidateKeys(caesarKeyApproximation);

			// Decryption
			Caesar caesar = new Caesar(alphabet, sortedOffsets.get(0));
			plainText = caesar.decode(cipherText);

			return sortedOffsets;
		}

		@Override
		protected void done() {
			try {
				Integer key = get().get(0);

				view.showWaitDialog(false);
				resultView.setPanelEnabled(true);

				resultView.showResults(String.valueOf(key),
						plainText.getContent());
			} catch (Exception e) {
				processCryptanalysisFailed(e);
			}
		}
	}

	private class VigenereCryptanalysisWorker extends
			SwingWorker<List<Integer>, Void> {
		private Alphabet alphabet;
		private Text plainText;
		private Text cipherText;

		public VigenereCryptanalysisWorker(Alphabet alphabet, Text cipherText) {
			this.alphabet = alphabet;
			this.cipherText = cipherText;
		}

		@Override
		protected List<Integer> doInBackground() throws Exception {
			CryptAnalysisVigenere cryptAnalysis = new CryptAnalysisVigenere(
					cipherText, alphabet);
			List<Integer> offsets = cryptAnalysis.cryptAnalyze();
			Vigenere vigenereCipher = new Vigenere(alphabet, offsets);
			plainText = vigenereCipher.decode(cipherText);

			return offsets;
		}

		@Override
		protected void done() {
			try {
				List<Integer> offsets = get();
				StringBuffer keyword = new StringBuffer();
				for (int n : offsets) {
					keyword.append(alphabet.getSymbolAt(n));
				}
				view.showWaitDialog(false);
				resultView.setPanelEnabled(true);

				resultView.showResults(keyword.toString(),
						plainText.getContent());

			} catch (Exception e) {
				processCryptanalysisFailed(e);
			}
		}
	}

	private class PlayfairCryptanalysisWorker extends SwingWorker<String, Void> {
		Alphabet alphabet;
		Text cipherText, plainText;
		int maxCandidates;
		int survivorsPercentage;
		int maxGenerations;

		public PlayfairCryptanalysisWorker(Alphabet alphabet, Text cipherText,
				int maxCandidates, int survivorsPercentage, int maxGenerations) {
			this.alphabet = alphabet;
			this.cipherText = cipherText;
			this.maxCandidates = maxCandidates;
			this.survivorsPercentage = survivorsPercentage;
			this.maxGenerations = maxGenerations;
		}

		@Override
		protected String doInBackground() throws Exception {
			CryptAnalysisPlayfair cryptAnalysisPlayfair = new CryptAnalysisPlayfair(
					cipherText, alphabet);
			String keyword = cryptAnalysisPlayfair.cryptAnalyze(maxCandidates,
					survivorsPercentage, maxGenerations);
			Playfair playfair = new Playfair(alphabet, keyword);
			this.plainText = playfair.decode(cipherText);

			return keyword;
		}

		@Override
		protected void done() {
			try {
				String keyword = get();
				view.showWaitDialog(false);
				resultView.setPanelEnabled(true);
				resultView.showResults(keyword, plainText.getContent());
				// System.gc();
			} catch (Exception e) {
				processCryptanalysisFailed(e);
			}
		}

	}

	private class HillCryptanalysisWorker extends
			SwingWorker<HillKeyMatrix, Void> {
		private Alphabet alphabet;
		private Text cipherText, plainText;

		private String plainTextChunk;
		private int ngramSize;

		public HillCryptanalysisWorker(Alphabet alphabet, Text cipherText,
				String plainTextChunk, int ngramSize) {
			this.alphabet = alphabet;
			this.cipherText = cipherText;
			this.plainTextChunk = plainTextChunk;
			// this.cipherTextChunk = cipherTextChunk;
			this.ngramSize = ngramSize;
		}

		@Override
		protected HillKeyMatrix doInBackground() throws Exception {
			CryptAnalysisHill cryptAnalysisHill = new CryptAnalysisHill(
					alphabet, originalText);
			HillKeyMatrix key = cryptAnalysisHill.cryptAnalyze(plainTextChunk,
					ngramSize);

			// Decodification
			Hill hill = new Hill(alphabet, key);
			plainText = hill.decode(cipherText);
			return key;
		}

		@Override
		protected void done() {
			try {
				HillKeyMatrix key = get();
				String keyword = keyToKeyword(key);
				view.showWaitDialog(false);
				resultView.setPanelEnabled(true);
				resultView.showResults(keyword, plainText.getContent());
			} catch (Exception e) {
				processCryptanalysisFailed(e);
			}
		}

		private String keyToKeyword(HillKeyMatrix key) {
			StringBuffer keyword = new StringBuffer();
			for (int n : key.getAsVector()) {
				keyword.append(alphabet.getSymbolAt(n));
			}
			return keyword.toString();
		}
	}

	private void processCryptanalysisFailed(Exception e) {
		view.showWaitDialog(false);
		view.showExceptionDialog(e.getCause().getMessage());
		this.resultView.showResults("", "");
		this.resultView.setPanelEnabled(false);
	}

	@Override
	public void update(Observable o, Object arg) {
		if (o instanceof TextTypeDAO) {
			textTypes = TextTypeDAO.getInstance().findAll();
			textOptionsView.showTypes(textTypes);
		}
		if (o instanceof TextLanguageDAO) {
			textLanguages = TextLanguageDAO.getInstance().findAll();
			textOptionsView.showLanguages(textLanguages);
		}
		if (o instanceof AlphabetDAO) {
			int cipher = optionsView.getSelectedCipherIndex();
			if (cipher > 0) {
				showAlphabetsIfEnabled(cipher);
			}
		}
	}

	private void showAlphabetsIfEnabled(int cipher) {
		this.alphabets = AlphabetDAO.getInstance().findByCipher(cipher);
		List<String> alphabetsNames = new ArrayList<String>();
		for (Alphabet alphabet : alphabets) {
			alphabetsNames.add(alphabet.getName());
		}
		optionsView.showAlphabetsIfEnabled(alphabetsNames);
	}

}
